package com.leo.boot.feature.merge2;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor;
import static org.apache.commons.lang3.reflect.MethodUtils.getMatchingMethod;

@Slf4j
public class FieldDelegate {

    FieldMerging fieldMerging;

    Pair<Class<?>, Method> get;

    Pair<Class<?>, Method> set;

    Pair<Class<?>, Method> op;

    Pair<Class<?>, Method> test;

    Object nil;

    boolean ignore = false;

    @SneakyThrows
    FieldDelegate(Field field) {
        Class<?> clazz = field.getDeclaringClass();
        Class<?> type = field.getType();
        this.get = ImmutablePair.of(clazz, getMatchingMethod(clazz, "get" + capitalize(field.getName())));
        this.set = ImmutablePair.of(clazz, getMatchingMethod(clazz, "set" + capitalize(field.getName()), type));
        this.fieldMerging = field.getAnnotation(FieldMerging.class);
        if (this.fieldMerging == null) {
            return;
        }

        if (isNotBlank(fieldMerging.op())) {
            this.op = ImmutablePair.of(clazz, getMatchingMethod(clazz, fieldMerging.op(), type, type));
        }
        if (isNotBlank(fieldMerging.test())) {
            this.test = ImmutablePair.of(clazz, getMatchingMethod(clazz, fieldMerging.test(), type, type));
        }
        if (isNotBlank(fieldMerging.nil())) {
            this.nil = invokeConstructor(type, fieldMerging.nil());
        }
        this.ignore = fieldMerging.ignore();
    }

    @SneakyThrows
    public Object get(Object obj) {
        return get.getRight().invoke(get.getLeft(), obj);
    }

    @SneakyThrows
    public void set(Object obj, Object value) {
        set.getRight().invoke(set.getLeft(), obj, value);
    }

    @SneakyThrows
    public Object op(Object a, Object b) {
        if (op == null) {
            return null;
        }
        return op.getRight().invoke(op.getLeft(), a, b);
    }

    @SneakyThrows
    public boolean test(Object a, Object b) {
        if (test == null) {
            return true;
        }
        return (boolean) test.getRight().invoke(test.getLeft(), a, b);
    }

    public Object nil() {
        return nil;
    }

    public boolean ignore() {
        return ignore;
    }
}
